package middleware

import (
	"context"
	"fmt"
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"google.golang.org/genproto/googleapis/rpc/errdetails"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"

	"wxpay/internal/conf"
	"wxpay/pkg/mcontext"
	"wxpay/pkg/merrors"
	"wxpay/pkg/notify"
)

//request中间件 将request_id 记录到请求日志中
func addRequestId(ctx context.Context) (context.Context, error) {
	md, _ := metadata.FromIncomingContext(ctx)
	var requestId string
	if val, ok := md["x-request-id"]; ok {
		requestId = val[0]
	} else {
		requestId = uuid.New().String()
	}
	//将请求的request_id 记录到日志中间件中
	//这样在本次请求的其他的日志都可以获取到RequestId,方便日志跟踪
	ctx = mcontext.WithRqId(ctx, requestId)
	return ctx, nil
}

// 服务器端的单向调用的拦截器
func LogicIntInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	const caller = "int_interceptor"
	defer merrors.LogPanic(ctx, caller, req, info, &err)
	ctx, err = addRequestId(ctx)

	resp, err = handler(ctx, req)
	logger := mcontext.Logger(ctx)
	//logger.Info(caller, zap.Any("info", info), zap.Any("req", req), zap.Any("resp", resp), zap.Error(err))

	pm := notify.PostMan{L: notify.SystemErrorWechatNotify{Ctx: ctx, Err: err, Env: conf.Conf.Env.Name}}
	pm.Notify()

	s, ok := status.FromError(err)
	if s.Code() != 0 && s.Code() < 1000 {
		md, _ := metadata.FromIncomingContext(ctx)
		logger.Error(caller, zap.String("method", info.FullMethod), zap.Any("md", md),
			zap.Any("resp", resp), zap.Error(err))
	}
	if !ok && err != nil {
		v, success := err.(*merrors.Error)
		if success {
			// 包装成grpc status
			s := status.Newf(v.ToGrpcCode(), "%s", v.CustomerErrorMsg())
			m := map[string]string{"ErrorCode": v.CustomerErrorCode(), "ErrorMessage": v.CustomerErrorMsg()}
			s, _ = s.WithDetails(&errdetails.ErrorInfo{
				Reason:   v.ToGrpcCode().String(),
				Domain:   "mall",
				Metadata: m,
			})
			err = s.Err()
		} else {
			err = status.Errorf(codes.Unknown, "%s", err)
		}
	}
	return resp, err
}

var validate = validator.New()

func ValidateInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	if err = validate.Struct(req); err != nil {
		err = merrors.E(merrors.Op("ValidateInterceptor"), merrors.InvalidArgument, err.Error(), merrors.ErrInvalidArguments)
		return
	}
	resp, err = handler(ctx, req)
	return
}

func ClientInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	start := time.Now()
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()
	err := invoker(ctx, method, req, reply, cc, opts...)
	mcontext.Logger(ctx).Info(fmt.Sprintf("Invoked RPC method=%s; req=%v; resp=%s, Duration=%s; Error=%v", method, req, reply, time.Since(start), err))
	return err
}
